// Authentication Manager - In core authentication GUI using libvisual
// Copyright 2005 (C) Ralph Thomas


#include <string>
#include <iostream>
#include <boost/config.hpp>
#include <boost/bind.hpp>
#include <boost/any.hpp>

#include <client_assembler.hpp>
#include <factory.hpp>

#include <adobe/config.hpp>
#include <adobe/name.hpp>
#include <adobe/value.hpp>
#include <adobe/dictionary.hpp>
#include <adobe/array.hpp>
#include <adobe/adam.hpp>
#include <adobe/adam_evaluate.hpp>
#include <adobe/adam_parser.hpp>
#include <adobe/istream.hpp>

#include <fstream>
#include <sstream>
#include <map>

#include <model/internal/authenticationManager.h>
#include <model/attributeNames.h>
#include <model/actionNames.h>
#include <model/error.h>
#include <model/model.h>

//
// Include our ASL definitions. We don't keep these in files because
// the model is library code (and shouldn't impose runtime requirements
// like external files on it's users).
//
#include <model/internal/authenticationManager-asl.h>

namespace model {
	//
	/// The authController class creates and manages the GUI for
	/// authentication. Once the user has supplied authentication
	/// information, it caches the username and password combination
	/// so that it can be used again for other models without
	/// annoying the user.
	///
	/// It doesn't store authentication data, so there is no concept
	/// of session management.
	//
	class authController {
		typedef std::map<util::string, authenticationManager::account>
			authMap;
		model* _model;			///< The model to authenticate
		adobe::sheet_t _authSheet;	///< Sheet containing account
		eve_client::window_server_t* _ws;	///< Window container.
		util::string _token;		///< Unique auth token.
		static authMap _authMap;	///< Cached auth keys.
	  public:
		//
		// authController( util::string token, const model* m )
		//
		/// Create a new authController. This will create a GUI
		/// requesting a username and password. It will send the
		/// kAuthenticate message to the model when it gets the
		/// password.
		///
		/// \param	m	the model to tell the username and
		///			password to.
		//
		authController( util::string token, const model* m )
		 : _model( 0 ), _ws( 0 ), _token( token ) {
			//
			// Check that we were given a real model!
			//
			if( m ) _model = m->retain();
		}
		//
		// ~authController()
		//
		/// Release the model, if it was retained.
		//
		virtual ~authController() {
			if( _model ) _model->release();
			if( _ws ) delete _ws;
		}
		//
		// void action(const adobe::name_t& name,
		//	const adobe::value_t& value)
		//
		/// This function is called by the GUI when some action
		/// button is pressed. We are interested in the button
		/// named "authenticate" -- when that gets pressed we read
		/// the username and password out of the sheet and send them
		/// to the model.
		///
		/// \param	name	the name of the pressed button.
		/// \param	value	the value of the @value of the button.
		//
		void action(const adobe::name_t& name, const adobe::value_t&
			value) {
			//
			// Is this the interesting "authenticate" button?
			//
			if( std::string( name.get() ) != "authenticate" ) return;
			//
			// The authenticate button was pressed. Hopefully the
			// button's value is the username and password we need.
			// These values are encoded in a dictionary, typically.
			//
			// We try to extract the contributing value dictionary
			// and the account (result) dictionary from that.
			//
			authenticationManager::account authInfo;
			try {
				adobe::static_name_t username_k( "username" );
				adobe::static_name_t password_k( "password" );
				adobe::static_name_t value_k( "value" );
				adobe::dictionary_t accountDict =
					value.get<adobe::dictionary_t>().get<adobe::dictionary_t>( value_k );
				authInfo.username = accountDict.get<adobe::value_t>( username_k ).get<std::string>();
				authInfo.password = accountDict.get<adobe::value_t>( password_k ).get<std::string>();
			} catch( ... ) {
				//
				// Something went wrong when we tried to get
				// the authentication information out of the
				// dialog. Tell the model that we couldn't
				// authenticate.
				//
				if( _model )
					_model->setAttribute( kError,
					adobe::value_t( (int)kErrorCantAuth ) );
				return;
			}
			//
			// Now cache this authentication information. It's
			// important that we cache before telling the model,
			// because the model might be able to say "that's
			// wrong" and tell authenticationManager to forget
			// the combination (so if we cached it *after* the
			// model tried to get rid of it, it would never go).
			//
			_authMap[_token] = authInfo;
			//
			// Now tell the model about our authentication
			// information.
			//
			if( _model ) _model->execute( kAuthenticate,
				adobe::value_t( authInfo ) );
			//
			// We are done -- we can destroy ourselves.
			//
			delete this;
		}
		//
		// bool init()
		//
		/// Attempt to create the authentication GUI and present it to
		/// the customer.
		///
		/// \return	true if the GUI was created and displayed,
		///		false if the GUI couldn't be created.
		//
		bool init() {
			//
			// First we need to load up our sheet. Currently
			// the sheet comes from a stringstream assembled
			// here.
			//
			std::stringstream sheet;
			std::stringstream gui;
			sheet << AUTH_ASL_ADAM << std::endl;
			gui << AUTH_ASL_EVE << std::endl;
			//
			// Now we can load that sheet definition into our
			// sheet.
			//
			try {
			adobe::parse( sheet,
				adobe::line_position_t( "internal-auth.adm" ),
				adobe::bind_to_sheet( _authSheet ) );
			} catch( ... ) {
				//
				// Well, that didn't work! Tell the model that
				// authentication failed and bail out.
				//
				if( _model ) _model->setAttribute( kError,
					adobe::value_t( (int)kErrorCantAuth ) );
				return false;
			}
			//
			// We can load up the GUI. Currently this _has_ to
			// come from a file, because the client_assembler
			// doesn't know about istreams.
			//
			_ws = new eve_client::window_server_t( "", _authSheet );
			//
			// Make sure that we get told about events happening
			// on the window we create.
			//
			_ws->set_action_fallback( boost::bind(
				&authController::action, this, _1, _2 ) );
			//
			// Now load our authentication window.
			//
			try {
				_ws->push_back( gui,
					adobe::line_position_t::ident_pair_t(adobe::name_t("auth-gui"), 0),
					size_normal_s );
			} catch( ... ) {
				if( _model ) _model->setAttribute( kError,
					adobe::value_t( (int)kErrorCantAuth ) );
				return false;
			}
			return true;
		}
		//
		// bool getAuthInfo( util::string token,
		//		authenticationManager::account& a )
		//
		/// Try to get the account information for the given token. If
		/// this information is known, then it is set into the given
		/// account reference and true is returned. If this information
		/// is not known then false is returned.
		///
		/// \param	token	the token to authenticate with.
		/// \param	a	the account info to fill out.
		///
		/// \return	true if the account information was known,
		///		false otherwise.
		//
		static bool getAuthInfo( util::string token, authenticationManager::account& a ) {
			authMap::iterator i = _authMap.find( token );
			if( i == _authMap.end() ) return false;
			a = i->second;
			return true;
		}
	};

	std::map<util::string, authenticationManager::account> authController::_authMap;

	void authenticationManager::getAccountInfo( util::string token, model* m ) {
		account a;
		//
		// We need a model to tell about the authentication information.
		// We can't do anything useful without a model.
		//
		if( !m ) return;
		//
		// If we already know the information for this token then we can
		// simply return that to the model.
		//
		if( authController::getAuthInfo( token, a ) ) {
			m->execute( kAuthenticate, adobe::value_t( a ) );
			return;
		}
		//
		// We need to present the user with the authentication GUI
		// before we can continue.
		//
		authController* ac = new authController( token, m );
		if( !ac->init() ) delete ac;
	}
};

